﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Directory = Lucene.Net.Store.Directory;
using LnVersion = Lucene.Net.Util.Version;

namespace LuceneApiServer
{
    /// <summary>
    ///     Lucene.Net操作封装
    /// </summary>
    internal static class LnHelper
    {
        /// <summary>
        ///     写入锁
        /// </summary>
        private static readonly object WriterSyncRoot = new object();

        /// <summary>
        ///     分析器
        /// </summary>
        private static readonly Analyzer Analyzer = new PanGuAnalyzer();

        /// <summary>
        ///     索引目录
        /// </summary>
        private static readonly ConcurrentDictionary<string, Directory> IndexStores =
            new ConcurrentDictionary<string, Directory>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        ///     获取索引是否已存在
        /// </summary>
        /// <param name="indexName">索引名</param>
        /// <returns></returns>
        private static bool ExistsIndex(string indexName)
        {
            return File.Exists(Path.Combine("Indexs/" + indexName, "segments.gen"));
        }

        /// <summary>
        ///     获取索引存储目录
        /// </summary>
        /// <param name="indexName">索引名</param>
        /// <returns></returns>
        private static Directory GetIndexStore(string indexName)
        {
            return IndexStores.GetOrAdd(indexName, key =>
            {
                var indexPath = "Indexs/" + key;
                if (!System.IO.Directory.Exists(indexPath))
                {
                    System.IO.Directory.CreateDirectory(indexPath);
                }
                var dirInfo = new DirectoryInfo(indexPath);
                return FSDirectory.Open(dirInfo);
            });
        }

        /// <summary>
        ///     提供安全执行Reader操作
        /// </summary>
        /// <typeparam name="T">返回类型</typeparam>
        /// <param name="indexName">索引名</param>
        /// <param name="func">Func</param>
        /// <returns></returns>
        private static T InvokeReaderNoLock<T>(string indexName, Func<IndexReader, T> func)
        {
            var indexStore = GetIndexStore(indexName);
            using (var reader = IndexReader.Open(indexStore, true))
            {
                return func(reader);
            }
        }

        /// <summary>
        ///     提供安全执行Writer操作
        /// </summary>
        /// <param name="indexName">索引名</param>
        /// <param name="action">Action</param>
        private static void InvokeWriterWithLock(string indexName, Action<IndexWriter> action)
        {
            lock (WriterSyncRoot)
            {
                var indexStore = GetIndexStore(indexName);
                if (IndexWriter.IsLocked(indexStore))
                {
                    IndexWriter.Unlock(indexStore);
                }
                using (
                    var writer = new IndexWriter(indexStore, Analyzer, !ExistsIndex(indexName),
                        IndexWriter.MaxFieldLength.LIMITED))
                {
                    action(writer);
                    writer.Commit();
                }
            }
        }

        /// <summary>
        ///     创建或更新到索引
        /// </summary>
        /// <param name="indexName">索引名称</param>
        /// <param name="models">模型</param>
        /// <returns></returns>
        public static bool SetIndex(string indexName, IEnumerable<LnModel> models)
        {
            if (models == null) return false;
            InvokeWriterWithLock(indexName, writer =>
            {
                foreach (var model in models)
                {
                    writer.UpdateDocument(model.GetTerm(), model.ToDocument());
                    //writer.DeleteDocuments(model.GetTerm());
                    //writer.AddDocument(model.ToDocument());
                }
            });
            return true;
        }

        /// <summary>
        ///     删除索引的全部记录
        /// </summary>
        /// <param name="indexName">索引名称</param>
        /// <returns></returns>
        public static bool DeleteIndex(string indexName)
        {
            InvokeWriterWithLock(indexName, writer => { writer.DeleteAll(); });
            return true;
        }

        /// <summary>
        ///     删除索引的记录
        /// </summary>
        /// <param name="indexName">索引名称</param>
        /// <param name="idFields">记录的Id字段</param>
        /// <returns></returns>
        public static bool DeleteIndex(string indexName, IEnumerable<LnField> idFields)
        {
            if (idFields == null) return false;
            InvokeWriterWithLock(indexName,
                writer => { writer.DeleteDocuments(idFields.Select(item => item.ToTerm()).ToArray()); });
            return true;
        }

        /// <summary>
        ///     优化索引
        /// </summary>
        /// <param name="indexName"></param>
        /// <returns></returns>
        public static bool OptimizeIndex(string indexName)
        {
            InvokeWriterWithLock(indexName, writer => { writer.Optimize(); });
            return true;
        }

        private static string GetKeyWordsSplitBySpace(string keywords, PanGuTokenizer ktTokenizer)
        {
            var buffer = new StringBuilder();
            var words = ktTokenizer.SegmentToWordInfos(keywords);
            if (words.Count > 1)
            {
                foreach (var word in words)
                {
                    if (word == null)
                        continue;
                    buffer.AppendFormat("{0}^{1}.0 ", word.Word, (int)Math.Pow(3, word.Rank));
                }
                return buffer.ToString().Trim();
            }
            return keywords.Trim();
        }

        /// <summary>
        ///     搜索索引
        /// </summary>
        /// <param name="q">查询对象</param>
        /// <returns></returns>
        public static LnPage SearchIndex(LnQuery q)
        {
            if (q == null || ExistsIndex(q.IndexName) == false)
            {
                return new LnPage { Models = new LnModel[0] };
            }

            return InvokeReaderNoLock(q.IndexName, reader =>
            {
                using (var searcher = new IndexSearcher(reader))
                {
                    var booleanQuery2 = new BooleanQuery();
                    var matchFields = q.MatchFields == null ? null : q.MatchFields.Select(item => item.Name).ToArray();
                    if (matchFields != null)
                    {
                        Query query;
                        foreach (
                            var field in
                                q.MatchFields.Where(item => item.IsTerm && !string.IsNullOrWhiteSpace(item.TermKeyword))
                                    .ToArray())
                        {
                            if (field.Index == Field.Index.ANALYZED || field.Index == Field.Index.ANALYZED_NO_NORMS)
                            {
                                var cl = new PanGuTokenizer().SegmentToWordInfos(field.TermKeyword);
                                foreach (var word in cl)
                                {
                                    if (word == null || string.IsNullOrWhiteSpace(word.Word))
                                        continue;
                                    query = new TermQuery(new Term(field.Name, word.Word));
                                    booleanQuery2.Add(query, Occur.MUST);
                                }
                            }
                            else
                            {
                                query = new TermQuery(new Term(field.Name, field.TermKeyword));
                                booleanQuery2.Add(query, Occur.MUST);
                            }
                        }
                        if (!string.IsNullOrWhiteSpace(q.Keywords))
                        {
                            var keys = q.Keywords.Split(new[] { ' ', ',', '，', '、' }, StringSplitOptions.RemoveEmptyEntries);
                            var booleanQuery = new BooleanQuery();
                            var parser = new MultiFieldQueryParser(LnVersion.LUCENE_30, matchFields, Analyzer)
                            {
                                DefaultOperator = QueryParser.Operator.OR
                            };
                            query = parser.Parse(GetKeyWordsSplitBySpace(q.Keywords, new PanGuTokenizer()));
                            // query = parser.Parse(q.Keywords);
                            booleanQuery.Add(query, Occur.SHOULD);
                            foreach (var word in keys)
                            {
                                foreach (
                                    var field in
                                        q.MatchFields.Where(
                                            item => item.IsTerm).ToArray())
                                {
                                    query = new WildcardQuery(new Term(field.Name, "*" + word.ToLower() + "*"));
                                    booleanQuery.Add(query, Occur.SHOULD);
                                }
                            }
                            booleanQuery2.Add(booleanQuery, Occur.MUST);
                        }
                    }
                    if (booleanQuery2.Clauses.Count == 0)
                    {
                        booleanQuery2.Add(new MatchAllDocsQuery(), Occur.MUST);
                    }

                    Sort sort = null;
                    if (q.SortFields != null && q.SortFields.Length > 0)
                    {
                        sort = new Sort(q.SortFields.Select(f => f.ToSortField()).ToArray());
                    }

                    try
                    {
                        var docs = sort == null
                            ? searcher.Search(booleanQuery2, null, q.Take + q.Skip)
                            : searcher.Search(booleanQuery2, null, q.Take + q.Skip, sort);
                        var models =
                            docs.ScoreDocs.Skip(q.Skip)
                                .Take(q.Take)
                                .Select(item => DocToLnModel(searcher.Doc(item.Doc)))
                                .ToArray();
                        if (matchFields != null && q.CanSetHighLight)
                        {
                            foreach (var model in models)
                            {
                                model.SetHighLight(q.Keywords, q.MatchFields);
                            }
                        }
                        return new LnPage
                        {
                            Models = models,
                            TotalCount = docs.TotalHits
                        };
                    }
                    catch (Exception)
                    {
                        return new LnPage
                        {
                            Models = new LnModel[0],
                            TotalCount = 0
                        };
                    }
                }
            });
        }

        public static LnIdsPage SearchIndexIds(LnQuery q)
        {
            if (q == null || ExistsIndex(q.IndexName) == false)
            {
                return new LnIdsPage { Ids =new List<string>() };
            }

            return InvokeReaderNoLock(q.IndexName, reader =>
            {
                using (var searcher = new IndexSearcher(reader))
                {
                    var booleanQuery2 = new BooleanQuery();
                    var matchFields = q.MatchFields == null ? null : q.MatchFields.Select(item => item.Name).ToArray();
                    if (matchFields != null)
                    {
                        Query query;
                        foreach (
                            var field in
                                q.MatchFields.Where(item => item.IsTerm && !string.IsNullOrWhiteSpace(item.TermKeyword))
                                    .ToArray())
                        {
                            if (field.Index == Field.Index.ANALYZED || field.Index == Field.Index.ANALYZED_NO_NORMS)
                            {
                                var cl = new PanGuTokenizer().SegmentToWordInfos(field.TermKeyword);
                                foreach (var word in cl)
                                {
                                    if (word == null || string.IsNullOrWhiteSpace(word.Word))
                                        continue;
                                    query = new TermQuery(new Term(field.Name, word.Word));
                                    booleanQuery2.Add(query, Occur.MUST);
                                }
                            }
                            else
                            {
                                query = new TermQuery(new Term(field.Name, field.TermKeyword));
                                booleanQuery2.Add(query, Occur.MUST);
                            }
                        }
                        if (!string.IsNullOrWhiteSpace(q.Keywords))
                        {
                            var keys = q.Keywords.Split(new[] { ' ', ',', '，', '、' }, StringSplitOptions.RemoveEmptyEntries);
                            var booleanQuery = new BooleanQuery();
                            var parser = new MultiFieldQueryParser(LnVersion.LUCENE_30, matchFields, Analyzer)
                            {
                                DefaultOperator = QueryParser.Operator.OR
                            };
                            query = parser.Parse(GetKeyWordsSplitBySpace(q.Keywords, new PanGuTokenizer()));
                            // query = parser.Parse(q.Keywords);
                            booleanQuery.Add(query, Occur.SHOULD);
                            foreach (var word in keys)
                            {
                                foreach (
                                    var field in
                                        q.MatchFields.Where(
                                            item => item.IsTerm).ToArray())
                                {
                                    query = new WildcardQuery(new Term(field.Name, "*" + word.ToLower() + "*"));
                                    booleanQuery.Add(query, Occur.SHOULD);
                                }
                            }
                            booleanQuery2.Add(booleanQuery, Occur.MUST);
                        }
                    }
                    if (booleanQuery2.Clauses.Count == 0)
                    {
                        booleanQuery2.Add(new MatchAllDocsQuery(), Occur.MUST);
                    }
                    Sort sort = null;
                    if (q.SortFields != null && q.SortFields.Length > 0)
                    {
                        sort = new Sort(q.SortFields.Select(f => f.ToSortField()).ToArray());
                    }

                    try
                    {
                        var docs = sort == null
                            ? searcher.Search(booleanQuery2, null, q.Take + q.Skip)
                            : searcher.Search(booleanQuery2, null, q.Take + q.Skip, sort);
                        var models =
                            docs.ScoreDocs.Skip(q.Skip)
                                .Take(q.Take)
                                .Select(item => searcher.Doc(item.Doc).Get("id"))
                                .ToList();

                        return new LnIdsPage { Ids = models, TotalCount = docs.TotalHits };
                    }
                    catch (Exception)
                    {
                        return new LnIdsPage { Ids =new List<string>() };
                    }
                }
            });
        }

        /// <summary>
        ///     Document对象转换为LnModel
        /// </summary>
        /// <param name="doc">Document</param>
        /// <returns></returns>
        private static LnModel DocToLnModel(Document doc)
        {
            var fields = doc.GetFields().Select(field => new LnField { Name = field.Name, Value = field.StringValue });
            return new LnModel(fields);
        }

        /// <summary>
        ///     合并索引
        /// </summary>
        /// <param name="fromIndex"></param>
        /// <param name="toIndex"></param>
        /// <param name="delfromindex"></param>
        public static void Merge(string fromIndex, string toIndex, bool delfromindex = false)
        {
            InvokeWriterWithLock(toIndex, writer =>
            {
                writer.MergeFactor = 100000;
                writer.SetMaxFieldLength(int.MaxValue);
                writer.SetMaxBufferedDocs(int.MaxValue);
                writer.MaxMergeDocs = int.MaxValue;
                if (InvokeReaderNoLock(fromIndex, reader =>
                {
                    writer.AddIndexes(reader);
                    return true;
                }) && delfromindex)
                {
                    DeleteIndex(fromIndex);
                }
                writer.Commit();
            });
        }
    }
}